#include "../include/grammaticCDT.h"

struct grammaticCDT
{
	char * name;
	listADT vn;
	listADT vt;
	char * initialSymbol;
	listADT productions;
	// flag to know if productions has a p that terminates only with a
	// VT symbol or has a p that terminates with lambda.
	int posibleValidProductions;

	// flag to know if productions has a p that start with
	// the initial symbol
	int hasInitialSymbol;

	// flag to know if it is a regular grammatic.
	int regular;

	int gramDirection;
};


// funciones auxiliares
/******************************************************/
char** split(const char* lim, char* src, int* size);
void freeArray(char** array, int num);
char * trim(char * p);
/******************************************************/

static void
Error(const char *cartel)
{
	printf("%s\n", cartel);
	exit(1);
}

// for listADT aux functions
int equals(listElementT x, listElementT y)
{
	char *a = (char*) x;
	char *b = (char*) y;
	return strcmp(a, b);
}

void freeListElement(listElementT x)
{
	free(x);
	return;
}

grammaticADT NewGrammatic(char * name)
{
	grammaticADT g;

	if ((g = malloc(sizeof(struct grammaticCDT))) == NULL) {
		Error("Memory error\n");
	}

	listADT vn = NewList(equals, freeListElement, 10);
	listADT vt = NewList(equals, freeListElement, 10);
	listADT productions = NewList(equals, freeListElement, 50);

	g->name = strdup(name);
	g->vn = vn;
 	g->vt = vt;
 	g->initialSymbol = NULL;
 	g->productions = productions;
 	g->gramDirection = NONE;
	g->posibleValidProductions = FALSE;
	g->hasInitialSymbol = FALSE;
	g->regular = TRUE;  //default it is true, when arrive a production which is not regular
						// will set it to FALSE.
	return g;
}

int AddInitialSymbol(grammaticADT g, char * symbol)
{	

	if (VNcontains(g, symbol) == FALSE)
	{
		return ERR;
	}
	
	g->initialSymbol = strdup(symbol);
	return OK;
}

int AddVN(grammaticADT g, char * symbol)
{
	if (VNcontains(g, symbol) == TRUE)
	{
		return ERR;
	}
	
	Insert(g->vn, symbol);
	return OK;
}

int AddVT(grammaticADT g, char * symbol)
{
	if (VTcontains(g, symbol))
	{
		return ERR;
	}
	
	Insert(g->vt, symbol);
	return OK;
}

int AddProduction(grammaticADT g, char * p)
{
 	if (IsRegularProduction(g, p) == FALSE)
 	{
 		g->regular = FALSE;
 	}

	if (g->initialSymbol == NULL || strlen(g->initialSymbol) == 0)
	{
		return ERR;
	}

	//char ** rightSymbols;
	char ** p_aux;
	char * leftPart;
	char * rightPart;
	int num = 0;

	p_aux = split(">", p, &num);
	if (num != 2)
	{
		// invalid production
		freeArray(p_aux, num);
		free(p_aux);
		return ERR;
	}

	leftPart = p_aux[0];
	rightPart = p_aux[1];

	int size, i;
	size = strlen(leftPart);
	for (i = 0; i < size; i++)
	{
		if (leftPart[i] != ' ')
		{
			char s [2] = {'\0'};
			s[0] = leftPart[i];
			if (VNcontains(g, s) == FALSE && VTcontains(g, s) == FALSE)
			{
				freeArray(p_aux, num);
				free(p_aux);
				return ERR;
			}

			if (strcmp(s, g->initialSymbol) == 0)
			{
				g->hasInitialSymbol = TRUE;
			}
		}
	}

	size = strlen(rightPart);
	for (i = 0; i < size; i++)
	{
		if (rightPart[i] != ' ' && rightPart[i] != '|')
		{
			char s [2] = {'\0'};
			s[0] = rightPart[i];
			if (s[0] != '\\' && VNcontains(g, s) == FALSE && VTcontains(g, s) == FALSE)
			{
				freeArray(p_aux, num);
				free(p_aux);
				return ERR;
			}
		}
	}

	freeArray(p_aux, num);
	free(p_aux);
	Insert(g->productions, p);
	return OK;	
}

int VNcontains(grammaticADT g, char * symbol)
{
	return ElementBelongsList(g->vn, symbol);
}

int VTcontains(grammaticADT g, char * symbol)
{
	return ElementBelongsList(g->vt, symbol);
}

int GrammaticDirection(grammaticADT g)
{
	return g->gramDirection;
}

int GetDirection(grammaticADT g, char * p)
{
	char l [2] = {'\0'};
	char r [2] = {'\0'};

	l[0] = p[0];
	r[0] = p[1];
	
	if (VNcontains(g, l) == TRUE)
	{
		// the symbol is on the right
		if (VTcontains(g, r) == TRUE)
		{
			return LEFT;
		}
		return ERR;
	}

	if (VTcontains(g, l) == TRUE)
	{
		// the symbol is on the left
		if (VNcontains(g, r) == TRUE)
		{
			return RIGHT;
		}
		return ERR;
	}

	return ERR;
}

void ShowVNs(grammaticADT g, FILE * f) {
	listADT vn = g->vn;
	int i;
	SetBegin(vn);
	fprintf(f, "Non-Terminal symbols\n");
	fprintf(f, "--------------------\n");
	fprintf(f, "Count: %d\n", GetListCount(vn));
	fprintf(f, "Table: ");
	fprintf(f, "{ ");
	for (i = 0; i < GetListCount(vn); i++) {
		char e [20] = {'\0'};
		GetDato(vn, &e);
		
		if (i == GetListCount(vn) - 1)
		{
			fprintf(f, "%s }\n",e);
		}
		else
		{
			fprintf(f, "%s, ", e);
		}
	}
	fprintf(f, "\n");
}

void ShowVTs(grammaticADT g, FILE * f) {
	listADT vt = g->vt;	
	int i;
	SetBegin(vt);
	fprintf(f, "Terminal symbols\n");
	fprintf(f, "--------------------\n");
	fprintf(f, "Count: %d\n", GetListCount(vt));
	fprintf(f, "Table: ");
	fprintf(f, "{ ");
	for (i = 0; i < GetListCount(vt); i++) {
		char e [20] = {'\0'};
		GetDato(vt, &e);
		if (i == GetListCount(vt) - 1)
		{
			fprintf(f, "%s }\n",e);
		}
		else
		{
			fprintf(f, "%s, ", e);
		}
	}
	fprintf(f, "\n");
}

void ShowProductions(grammaticADT g, FILE * f) {
	listADT p = g->productions;
	
	int i;
	SetBegin(p);
	fprintf(f, "Productions\n");
	fprintf(f, "--------------------\n");
	fprintf(f, "Count: %d\n", GetListCount(p));
	fprintf(f, "Table: ");
	fprintf(f, "{ ");
	for (i = 0; i < GetListCount(p); i++) {
		char e [100] = {'\0'};
		GetDato(p, &e);
		if (i == GetListCount(p) - 1)
		{
			fprintf(f, "%s }\n",e);
		}
		else
		{
			fprintf(f, "%s, ", e);
		}
	}
	fprintf(f, "\n");
}

void ShowInitialSymbol(grammaticADT g, FILE * f)
{
	fprintf(f, "Initial Symbol: %s\n", g->initialSymbol);
	fprintf(f, "\n");
}

char * GetName(grammaticADT g)
{
	return g->name;
}

int IsValidGrammatic(grammaticADT g, char * msg)
{
	if (g->name == NULL || strlen(g->name) == 0)
	{
		strcpy(msg, "Name of grammatic is empty");
		return FALSE;
	}

	if (g->initialSymbol == NULL || strlen(g->initialSymbol) == 0)
	{
		strcpy(msg, "Initial symbol is empty");
		return FALSE;
	}

	if (g->hasInitialSymbol == FALSE)
	{
		strcpy(msg, "Productions does not have initial symbol");
		return FALSE;
	}

	if (ListIsEmpty(g->vn))
	{
		strcpy(msg, "VN (not terminal symbols) is empty");
		return FALSE;
	}

	if (ListIsEmpty(g->vt))
	{
		strcpy(msg, "VT (terminal symbols) is empty");
		return FALSE;
	}

	if (ListIsEmpty(g->productions))
	{
		strcpy(msg, "Productions is empty");
		return FALSE;
	}
	
	return TRUE;
}

void FreeGrammaticADT(grammaticADT g)
{
	free(g->name);
	FreeList(g->vn);
	FreeList(g->vt);
	FreeList(g->productions);
	free(g->initialSymbol);
	free(g);
}

int IsRegularGrammatic(grammaticADT g)
{
	if (g->hasInitialSymbol == FALSE)
	{
		return FALSE;
	}
	return g->regular;
}

int IsRegularProduction(grammaticADT g, char * p)
{
	char ** p_aux;
	char ** s_aux;
	char * leftPart;
	char * rightPart;
	int num = 0;
	
	p_aux = split(">", p, &num);
	if (num != 2)
	{
		freeArray(p_aux, num);
		free(p_aux);
		return FALSE;
	}

	leftPart = trim(p_aux[0]);
	rightPart = trim(p_aux[1]);

	char symbol;
	if (sscanf(leftPart, "%c", &symbol) != 1)
	{
		free(leftPart);
		free(rightPart);
		freeArray(p_aux, num);
		free(p_aux);
		return FALSE;
	}

	if (VNcontains(g, leftPart) == FALSE)
	{
		free(leftPart);
		free(rightPart);
		freeArray(p_aux, num);
		free(p_aux);
		return FALSE;
	}

	int s_num;
	s_aux = split("|", rightPart, &s_num);
	if (s_num == 0)
	{
		free(leftPart);
		free(rightPart);
		freeArray(p_aux, num);
		free(p_aux);
		freeArray(s_aux, s_num);
		free(s_aux);
		return FALSE;
	}

	free(leftPart);
	free(rightPart);

	int i;
	for (i = 0; i < s_num; i++)
	{
		// i have s_aux[i], i divide it to 2 symbol (rightSymbol and leftSymbol)
		char r, l;
		int matched;
		matched = sscanf(s_aux[i], "%c%c", &r, &l);
		if (matched == 1)
		{
			//only had one symbol, i check it if belongs to VT or it is lambda
			if (r != '\\')
			{
				if (VTcontains(g, s_aux[i]) == FALSE)
				{
					freeArray(p_aux, num);
					free(p_aux);
					freeArray(s_aux, s_num);
					free(s_aux);
					return FALSE;
				}
			}
		} else if (matched == 2)
		{
			int dir = GetDirection(g, s_aux[i]);
			if (dir == ERR)
			{
				freeArray(p_aux, num);
				free(p_aux);
				freeArray(s_aux, s_num);
				free(s_aux);
				return FALSE;
			}
			
			if (g->gramDirection == NONE)
			{
				g->gramDirection = dir;
			}

			if (dir != GrammaticDirection(g))
			{
				// adding a production with diferent direction than the grammatic
				freeArray(p_aux, num);
				free(p_aux);
				freeArray(s_aux, s_num);
				free(s_aux);
				return FALSE;
			}
			
		} else
		{
			freeArray(p_aux, num);
			free(p_aux);
			freeArray(s_aux, s_num);
			free(s_aux);
			return FALSE;
		}
	}

	freeArray(p_aux, num);
	free(p_aux);
	freeArray(s_aux, s_num);
	free(s_aux);
	//FINALLY
	return TRUE;
}





// funciones auxiliares

/**
Funcion que emula la funcion split de perl
@param lim el limitador
@param src el string original
@param size parametro de salida donde indicara la cantidad de strings que pudo "splitear"
@return un array de string separado por el limitador
*/
char** split(const char* lim, char* src, int* size) {
	char * token;
	int block = 20;
	int i = 0;
	
	char ** ret = malloc(sizeof(char *)*block);
	char * aux = malloc(sizeof(char)*(strlen(src)+1));
	
	strcpy(aux, src);
	
	if(aux == NULL || ret == NULL) {
		return NULL;
	}
	token = strtok(aux, lim);
	
	while(token != NULL) {
		if(i == block){
			if((ret=realloc(ret, (block+=block)*sizeof(char*))) == NULL) {
				freeArray(ret, i);
				free(ret);
				free(aux);
				return NULL;
			}
		}
		
		ret[i] = strdup(token);
		token = strtok(NULL, lim);
		i++;
	}
	
	if(size != NULL) {
		*size = i;
	}
	
	free(aux);
	return ret;
}


/**
Funcion que libera recursos alocados por un array
@param array array de strings
@param num tamanio del array
*/
void freeArray(char** array, int num) {
	int i;
	for(i=0; i<num; i++){
		free(array[i]);
	}
	return;
}

/**
*/
char * trim(char * p)
{
	char * ret = calloc(strlen(p), sizeof(char));
	int i = 0;
	int j = 0;
	while (p[i] != '\0')
	{
		if(p[i] != ' ')
		{
			ret[j++] = p[i];
		}
		i++;
	}
	return ret;
}
